5.07. Операторы и циклы
Операторы и циклы
В этом разделе последовательно рассматриваются категории операторов, их семантика, поведение в контексте типов данных, а также принципы построения и исполнения циклических и условных конструкций.
Классификация операторов в PHP
В PHP оператор определяется как языковая конструкция, принимающая один или несколько операндов и возвращающая результат вычисления. Операторы группируются по количеству операндов (унарные, бинарные, тернарные) и по функциональной принадлежности. Выделяют следующие основные группы: арифметические, строковые, присваивающие, сравнения, логические, побитовые, операторы ошибок, операторы объединения массивов, а также специальные операторы, такие как instanceof, yield, match, nullsafe и т.п. В рамках данной главы основное внимание уделено тем операторам, которые непосредственно участвуют в формировании условий и тел управляющих конструкций.
Арифметические операторы
Арифметические операторы предназначены для выполнения базовых математических действий над числовыми значениями: сложение, вычитание, умножение, деление, остаток от деления и возведение в степень. В PHP реализованы следующие арифметические операторы: +, -, *, /, %, **. Все они бинарные, за исключением унарных форм + и -, применяемых для явного указания знака числа.
Особое внимание требует оператор % (остаток от деления, modulo). В отличие от некоторых других языков (например, Python), в PHP остаток наследует знак делимого, а не делителя. Это означает, что результат -7 % 3 будет равен -1, тогда как в языках с «математически корректным» остатком он равен 2. Такое поведение соответствует реализации операции в процессорных инструкциях x86 и может быть критично при реализации алгоритмов, чувствительных к знаку (например, хеширования, кольцевых буферов).
Оператор ** (возведение в степень) появился в PHP 5.6 и позволяет записывать выражения вида $x ** $y, что эквивалентно вызову pow($x, $y), но без приведения аргументов к float. При использовании нецелых показателей результат может быть комплексным, но поскольку PHP не поддерживает комплексные числа, такое выражение порождает NAN.
Важно подчеркнуть, что PHP не гарантирует точного представления чисел с плавающей запятой в арифметических операциях из-за особенностей стандарта IEEE 754. Выражение вида 0.1 + 0.2 == 0.3 вернёт false. Для финансовых или точных вычислений рекомендуется использовать расширение bcmath или gmp, либо проводить сравнения с заданной погрешностью.
Операторы сравнения
Операторы сравнения возвращают логическое значение true или false, определяя отношение между двумя операндами. В PHP реализованы как «слабые» (==, !=, <>, <=, >=, <, >), так и «строгие» (===, !==) операторы сравнения.
Слабое равенство (==) проводит неявное приведение типов перед сравнением. Это означает, что разнотипные значения могут быть признаны равными: например, строка '0', целое 0, логическое false, пустой массив [] и null образуют сложную цепочку эквивалентностей. В частности, 0 == 'abc' возвращает true, поскольку строка 'abc' приводится к числу 0. Такое поведение может приводить к трудноуловимым ошибкам, особенно в условиях, где ожидаются строгие проверки.
Строгое равенство (===) требует совпадения как значения, так и типа. Это рекомендуемый подход в большинстве случаев, особенно при сравнении с константами (null, true, false), при валидации входных данных, при проверке возвращаемых значений функций. Например, strpos('abc', 'a') возвращает 0, и слабая проверка if (strpos(...) == false) даст ложноположительный результат, тогда как === false — корректна.
Операторы <, >, <=, >= также подвержены неявному приведению. Сравнение строк происходит побайтово (лексикографически), а если оба операнда не являются строками, они приводятся к числам. Это может приводить к неожиданному поведению: например, '10' < '2' — true, поскольку строковое сравнение идёт посимвольно: '1' < '2'. Для числового сравнения строк с цифрами необходимо предварительное приведение типов или использование функций вроде strcmp() с явным указанием контекста.
Логические операторы
Логические операторы применяются к операндам, интерпретируемым как логические значения, и возвращают true или false. В PHP реализованы следующие логические операторы: && (и), || (или), ! (отрицание), а также их альтернативные текстовые формы and, or, xor. Ключевое различие между символическими (&&, ||) и текстовыми (and, or) операторами — приоритет. Текстовые операторы имеют самый низкий приоритет, ниже, чем у операторов присваивания. Это означает, что выражение $a = true and false присвоит $a значение true, поскольку интерпретируется как ($a = true) and false, а не как $a = (true and false). По этой причине использование and/or в сложных выражениях считается плохой практикой и потенциальным источником ошибок.
PHP поддерживает short-circuit evaluation («короткое замыкание»): при вычислении A && B, если A ложно, B не вычисляется; при A || B, если A истинно, B не вычисляется. Это позволяет безопасно составлять цепочки проверок, например: if ($obj !== null && $obj->isActive()), где второе условие не будет вызвано, если $obj равен null.
Операторы присваивания
Оператор присваивания (=) в PHP не является выражением в строгом смысле (в отличие от C или JavaScript), но возвращает присвоенное значение, что позволяет строить цепочки: $a = $b = 5. PHP поддерживает составные операторы присваивания: +=, -=, *=, /=, %=, **=, .=, &=, |=, ^=, <<=, >>=. Все они эквивалентны явной операции с последующим присваиванием: $a += 1 равно $a = $a + 1.
Особое значение имеет оператор .= — конкатенация со строкой. PHP неявно приводит любой операнд к строке при использовании конкатенации, что делает эту операцию универсальной, но требующей внимания к типам: 123 . null даёт '123', true . false — '1', [] . 'x' — 'Arrayx', что может быть неожиданно.
Начиная с PHP 7.4, появился null coalescing assignment (??=), который присваивает значение, только если левый операнд равен null: $a ??= 'default' эквивалентно $a = $a ?? 'default'.
Тернарный оператор и оператор null coalescing
Тернарный оператор ?: позволяет компактно записать условное присваивание: условие ? выражение_если_истина : выражение_если_ложь. В PHP 5.3 появилась сокращённая форма expr1 ?: expr2, где expr1 вычисляется и возвращается, если оно истинно, иначе — expr2. Это часто называют Elvis operator. Однако важно помнить, что «истинность» определяется по правилам boolean context: 0, '0', '', [], null, false, 0.0 считаются ложными.
Оператор ?? (null coalescing) появился в PHP 7.0 и проверяет операнд на строгое равенство null, минуя проблемы с ложными, но не null-значениями. Выражение $a ?? 'default' возвращает 'default' только если $a действительно null; значение 0 или '' будет принято как валидное. Это особенно важно при работе с массивами и GET/POST-параметрами: $_GET['page'] ?? 1.
Оператор ?? также поддерживает цепочки: $a ?? $b ?? $c ?? 'fallback'.
Оператор match (PHP 8.0+)
С выходом PHP 8.0 был введён оператор match, представляющий собой функциональную альтернативу switch, но с рядом принципиальных отличий. Конструкция match всегда возвращает значение (это выражение, а не инструкция), использует строгое сравнение (===), не допускает «проваливания» между ветками и требует, чтобы все возможные значения были покрыты — либо явно, либо через default.
$status = match($role) {
'admin' => 'Администратор',
'editor' => 'Редактор',
default => 'Гость'
};
В отличие от switch, в match каждая ветка состоит только из выражения справа от =>, и не может содержать составных инструкций (циклы, присваивания с побочными эффектами). Это делает match пригодным только для выбора значения. При отсутствии совпадения и отсутствии ветки default выбрасывается исключение UnhandledMatchError.
Ключевое преимущество match — компактность, безопасность (отсутствие break, строгое сравнение) и возможность использования внутри выражений. Однако он не заменяет switch, когда требуется выполнить несколько операторов в одной ветке.
Условные конструкции: if, elseif, else
Конструкция if — фундаментальный инструмент управления потоком выполнения в PHP. Она позволяет выполнять блок кода только в том случае, если вычисляемое условие интерпретируется как истинное значение в boolean context. В отличие от операторов, if — это управляющая конструкция, а не выражение: она не возвращает значение и не может быть использована внутри других выражений (в отличие, например, от тернарного оператора или match).
Синтаксис прост:
if (условие) {
// блок, выполняемый при истинности условия
}
Условие может быть любым выражением: результат приводится к логическому типу по правилам, описанным в разделе о логическом контексте. Важно помнить, что в PHP отсутствует встроенная поддержка pattern matching или guards — проверка всегда сводится к булеву результату.
Конструкция допускает неограниченное количество веток elseif, каждая из которых проверяется последовательно, только если все предыдущие условия оказались ложными. Ветка else выполняется, если ни одно из предыдущих условий не было истинным. Поскольку проверка происходит строго по порядку, порядок elseif имеет значение: более специфичные условия должны располагаться раньше общих.
Пример:
if ($age > 18) {
echo "Взрослый";
} elseif ($age == 18) {
echo "Полных 18";
} else {
echo "Малыш";
}
Здесь критически важно, что проверка $age == 18 стоит после $age > 18. При ином порядке (== 18 перед > 18) значение 19 всё равно прошло бы первую ветку, но значение 18 никогда бы не достигло второй — это не ошибка логики в данном конкретном случае, но демонстрирует, как расположение условий влияет на поведение.
Особенности типизации в условиях
PHP не требует, чтобы условия были строго булевыми. Это открывает возможности, но также создаёт риски. Рассмотрим несколько ключевых сценариев:
-
Проверка существования переменной:
if ($var)не проверяет, определена ли переменная, а проверяет её истинность. Если$varне объявлена, будет сгенерировано notice-уровня предупреждениеUndefined variable. Для безопасной проверки следует использоватьisset($var) && $var, или, если допустимы ложные, но существующие значения —isset($var), или, если допустимnull—array_key_exists()для массивов,property_exists()для объектов. -
Проверка значений из внешних источников: параметры из
$_GET,$_POST,$_COOKIEвсегда являются строками (или массивами строк), даже если переданы цифры. Сравнение$_GET['id'] == 5приведёт к неявному приведению строки к числу, но строка'5abc'тоже превратится в5, и условие выполнится. Для строгой проверки —filter_var($_GET['id'], FILTER_VALIDATE_INT) !== false, либо явное приведение и строгое сравнение:(int)$_GET['id'] === 5, с оговоркой на потерю данных при некорректном вводе. -
Пустые структуры данных: пустой массив
[], пустая строка'', число0,0.0,false,null— все они ложны. Это позволяет компактно писатьif ($items) { … }, но может ввести в заблуждение, если ожидается, что массив из одного элемента['0']будет ложным (он — истинный, так как непуст).
Альтернативный синтаксис шаблонов
В контексте встраивания PHP в шаблоны (например, в чистых .php-файлах без фреймворков) допустим альтернативный синтаксис с ключевыми словами и двоеточиями:
<?php if ($auth): ?>
<p>Добро пожаловать, <?= htmlspecialchars($user) ?>!</p>
<?php elseif ($attempt > 3): ?>
<p class="error">Слишком много попыток.</p>
<?php else: ?>
<p>Пожалуйста, войдите.</p>
<?php endif; ?>
Этот стиль повышает читаемость в шаблонах, смешивающих логику и разметку, и предотвращает путаницу с фигурными скобками. Однако в чистом PHP-коде (классах, сервисах, CLI-скриптах) он не рекомендуется, поскольку нарушает единообразие стиля и не поддерживается всеми анализаторами кода на равных основаниях.
Циклические конструкции
Циклы в PHP предназначены для многократного выполнения блока кода при соблюдении определённых условий. Язык предоставляет четыре основные формы: for, while, do-while, foreach. Каждая из них оптимизирована под определённый тип итерации и имеет собственную семантику инициализации, проверки и обновления.
Цикл for: управляемая итерация с счётчиком
Конструкция for используется, когда заранее известно количество итераций или когда итерация управляется числовым счётчиком. Её синтаксис:
for (инициализация; условие; инкремент) {
// тело цикла
}
Все три компонента являются выражениями, и все они необязательны (можно написать for (;;) — бесконечный цикл). Порядок выполнения:
- Выполняется инициализация — один раз, до первой итерации.
- Проверяется условие. Если оно ложно — цикл завершается.
- Выполняется тело цикла.
- Выполняется инкремент.
- Возврат к шагу 2.
Пример:
for ($i = 0; $i < 5; $i++) {
echo $i;
}
Здесь $i = 0 — инициализация, $i < 5 — условие продолжения, $i++ — постинкремент. Обратите внимание: тело цикла выполнится при $i = 0, 1, 2, 3, 4, а при $i = 5 условие нарушится и цикл завершится. Переменная $i, объявленная в инициализации, остаётся в той же области видимости, что и сам цикл — в отличие от языков вроде C++ (до C++11) или Java, в PHP нет блочной области видимости для переменных цикла. Это означает, что после завершения цикла $i будет доступна и равна 5.
Цикл for допускает множественные выражения в каждом компоненте через запятую:
for ($i = 0, $j = 10; $i < $j; $i++, $j--) — хотя такая практика снижает читаемость и редко оправдана.
Важно: инкремент не обязан быть арифметическим. Можно использовать $i *= 2, $i = $i << 1, $i = nextPrime($i) — главное, чтобы условие в конечном итоге стало ложным, иначе цикл будет бесконечным.
Цикл while: итерация до выполнения условия
Цикл while проверяет условие перед каждой итерацией. Если условие изначально ложно, тело цикла не выполнится ни разу. Синтаксис:
while (условие) {
// тело
}
Пример:
$i = 0;
while ($i < 5) {
echo $i++;
}
Здесь инкремент $i++ выполняется внутри тела — именно как побочный эффект оператора вывода. Эта запись эквивалентна:
while ($i < 5) {
echo $i;
$i = $i + 1;
}
Разница лишь в компактности. Однако если условие зависит от внешнего состояния (например, чтение из потока, ожидание события, опрос API), while становится предпочтительным:
while ($socket = socket_accept($server)) {
handleConnection($socket);
}
Или:
while (($line = fgets($file)) !== false) {
processLine($line);
}
Такие паттерны используют побочный эффект присваивания внутри условия — распространённая, но требующая осторожности практика. Критически важно использовать строгое сравнение (!== false), поскольку возвращаемое значение может быть ложным, но валидным (например, строка '0' от fgets()).
Цикл do-while: итерация с постпроверкой
Конструкция do-while гарантирует, что тело цикла выполнится хотя бы один раз, поскольку проверка условия происходит после выполнения тела:
do {
// тело
} while (условие);
Это полезно, когда логика требует инициализации в первой итерации, например, при генерации уникального идентификатора до первого совпадения:
do {
$token = bin2hex(random_bytes(16));
} while (tokenExists($token));
Обратите внимание на точку с запятой после while — она обязательна, в отличие от for и while.
Цикл foreach: итерация по массивам и итерируемым объектам
Цикл foreach — наиболее часто используемая конструкция в PHP для обхода массивов и объектов, реализующих интерфейс Traversable (Iterator, IteratorAggregate, Generator и др.). Он абстрагирует детали внутреннего указателя массива и позволяет безопасно работать с данными, не заботясь о длине, индексации или порядке.
Базовый синтаксис для значений:
foreach ($array as $value) {
// обработка $value
}
Расширенный синтаксис — с ключами:
foreach ($array as $key => $value) {
// обработка $key и $value
}
Пример:
$fruits = ['яблоко', 'банан', 'вишня'];
foreach ($fruits as $fruit) {
echo $fruit . "<br>";
}
Внутренне PHP создаёт копию массива перед итерацией, если массив не помечен как ссылка и не используется модификация элементов. Это означает, что изменения $fruit внутри цикла не влияют на исходный массив $fruits. Для модификации элементов требуется явное указание ссылки:
foreach ($prices as &$price) {
$price *= 1.1; // повышаем на 10%
}
unset($price); // обязательно: разрыв ссылки во избежание побочных эффектов
Без unset($price) последующие манипуляции с $price (например, в другом цикле) могут неожиданно изменять последний элемент массива.
foreach корректно работает с ассоциативными массивами, числовыми, смешанными, вложенными структурами, а также с объектами. Для объектов по умолчанию итерируются публичные свойства; приватные и защищённые — только если объект реализует Iterator или IteratorAggregate.
Начиная с PHP 7.1, foreach поддерживает распаковку кортежей (deconstruction), если значения — массивы фиксированной структуры:
$points = [[1, 2], [3, 4], [5, 6]];
foreach ($points as [$x, $y]) {
echo "Точка ($x, $y)\n";
}
Это синтаксический сахар, повышающий выразительность и снижающий количество промежуточных переменных.
Вложенные циклы и управление потоком
Все циклы могут быть вложенными. Внутри цикла допустимы операторы break и continue, управляющие выходом или переходом к следующей итерации. Оба оператора принимают необязательный числовой аргумент, указывающий, сколько уровней вложенности прервать/продолжить:
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
if ($i === 1 && $j === 1) {
break 2; // выход из обоих циклов
}
echo "$i,$j ";
}
}
Злоупотребление break N снижает читаемость и усложняет рефакторинг; предпочтительнее выносить логику в функции с ранним возвратом.
Семантика итерации: что происходит внутри foreach
Хотя foreach выглядит как единая конструкция, его поведение определяется внутренними механизмами PHP по работе с итерируемыми сущностями — в первую очередь, с массивами (которые в PHP реализованы как упорядоченные хеш-таблицы) и объектами. Понимание этих механизмов критично для предсказуемости кода, особенно при модификации данных во время итерации.
Копирование массива и Copy-on-Write
Основной принцип: при входе в foreach PHP не всегда создаёт физическую копию массива. Вместо этого используется стратегия copy-on-write (копирование при записи). Если массив имеет единственный владелец (refcount = 1) и не является ссылкой, foreach получает прямой доступ к внутренней структуре. Если же массив используется в нескольких местах (например, передан в функцию или присвоен другой переменной), PHP помечает его как разделяемый, и при первой попытке изменения любого элемента (в том числе вне цикла) создаётся полная копия.
Это приводит к контринтуитивному поведению:
$a = [1, 2, 3];
$b = $a; // refcount = 2, $a и $b ссылаются на одну хеш-таблицу
foreach ($a as $v) {
$a[] = 10; // триггер copy-on-write: создаётся копия для $a
echo $v . ' ';
}
// Вывод: 1 2 3
// $b остаётся [1,2,3]; $a — [1,2,3,10,10,10]
Если убрать $b = $a, то foreach будет итерировать по изменяющемуся массиву, и цикл может стать бесконечным:
$a = [1, 2, 3];
foreach ($a as $v) {
$a[] = 10; // refcount = 1 → изменение in-place
echo $v . ' ';
if (count($a) > 10) break; // защита от бесконечности
}
// Вывод: 1 2 3 10 10 10 10 10 10 10 …
Такое поведение — следствие компромисса между производительностью (избегание лишних копий) и семантической целостностью. Рекомендация: никогда не модифицировать массив внутри foreach, если только вы не полностью контролируете его владение и не ожидаете побочных эффектов.
Изменение ключей и значений
При использовании синтаксиса foreach ($arr as $key => $value) переменные $key и $value — это локальные копии, даже если элемент массива является ссылкой. Присваивание $key = 100 не изменит ключ в исходном массиве (ключи в PHP неизменяемы после вставки). Присваивание $value = 100 изменит только локальную переменную, а не элемент массива.
Для изменения элементов требуется:
- Использование ссылки:
foreach ($arr as &$value). - Обращение по ключу:
foreach ($arr as $k => $v) { $arr[$k] = … }.
Второй подход безопаснее, так как не оставляет «висячих» ссылок. После foreach ($arr as &$v), переменная $v остаётся привязанной к последнему элементу массива. Если после этого выполнить $v = 'oops', последний элемент изменится независимо от контекста. Поэтому после цикла со ссылкой принято делать unset($v).
Итерация по объектам
Если объект не реализует Iterator или IteratorAggregate, foreach итерирует по его публичным свойствам. Приватные и защищённые свойства игнорируются. При этом порядок итерации соответствует порядку объявления свойств в коде класса, а не алфавитному или хеш-порядку.
При реализации Iterator, разработчик берёт на себя ответственность за логику rewind(), valid(), current(), key(), next(). Это позволяет создавать ленивые последовательности, фильтрацию «на лету», бесконечные генераторы — всё то, что не помещается в память целиком.
Генераторы и yield: ленивые итерации
Начиная с PHP 5.5, появилась поддержка генераторов через ключевое слово yield. Функция, содержащая yield, становится генератором — специальным типом Generator, реализующим Iterator. При вызове такая функция возвращает объект-итератор. Тело функции выполняется пошагово, только когда требуется следующее значение (например, при вызове next() из foreach).
Пример:
function xrange($start, $end) {
for ($i = $start; $i <= $end; $i++) {
yield $i;
}
}
foreach (xrange(1, 1000000) as $n) {
echo $n . "\n";
}
Здесь не создаётся массив из миллиона элементов. В памяти хранится только текущее значение $i и состояние стека генератора. Это позволяет эффективно работать с большими или потенциально бесконечными последовательностями.
Генератор может возвращать ключи: yield $key => $value, а также получать данные извне через yield в выражении: $input = yield $output, что открывает возможность двунаправленной корутиночной передачи (например, для асинхронных задач в рамках event loop).
Важно: объект Generator можно итерировать только один раз. Повторный foreach по тому же генератору не выведет ничего, так как внутренний указатель уже в конце. Для повторного использования нужно заново вызвать функцию-генератор.
Исторический контекст: от each() к foreach
В ранних версиях PHP (до 4.0) для итерации использовались функции each(), current(), next(), reset(), key(), работающие с внутренним указателем массива. Конструкция выглядела так:
reset($arr);
while (list($key, $value) = each($arr)) {
// обработка
}
Этот подход был хрупким: внутренний указатель массива общий для всего контекста, и вложенная итерация по тому же массиву сбивала позицию внешней. С приходом foreach (PHP 4) и, особенно, с переходом на хеш-таблицы с независимыми курсорами (PHP 7), ручное управление указателем стало избыточным и небезопасным. Функции each(), reset() и т.п. объявлены устаревшими в PHP 7.2 и удалены в PHP 8.0.
Современный foreach гарантирует изоляцию итераций: каждый цикл использует собственный итераторный курсор, не влияя на другие. Это делает код более предсказуемым и потокобезопасным в рамках одного процесса (хотя PHP по умолчанию не является многопоточным языком).
Сравнительный анализ: PHP vs C# vs Python vs JavaScript
Для понимания места PHP в экосистеме полезно сопоставить его управляющие конструкции с аналогами в других языках, которыми вы владеете.
Сравнение циклов
| Критерий | PHP | C# | Python | JavaScript (ES6+) |
|---|---|---|---|---|
| Счётчиковый цикл | for ($i=0; $i<10; $i++) | for (int i=0; i<10; i++) | for i in range(10): | for (let i=0; i<10; i++) |
| Условный цикл | while ($cond), do-while | while (cond), do { } while (cond) | while cond: | while (cond), do { } while (cond) |
| Цикл по коллекции | foreach ($arr as $v) | foreach (var v in arr) | for v in seq: | for (const v of arr) |
| Поддержка индексов | $key => $value | (index, value) через Select | enumerate(seq) | arr.entries() или for..in (для ключей) |
| Ленивые итерации | yield в генераторах | yield return в методах | yield в функциях | function* и yield |
| Разрыв по уровню | break 2, continue 3 | только break/continue (метки — редко) | break, continue | break, continue, метки |
Ключевые отличия:
- PHP и JavaScript используют C-подобный синтаксис
for, но в PHP переменные не имеют блочной области видимости, в отличие отlet/constв JS иvar/локальных в C#. - Python отказывается от индексных циклов в пользу итерации по последовательностям, что повышает безопасность, но снижает контроль над счётчиком.
- C# интегрирует LINQ, где
foreachчасто заменяется цепочкамиWhere().Select().ToList(), что смещает парадигму в сторону функционального стиля. - PHP остаётся ближе к императивной модели, но постепенно вбирает элементы функционального программирования:
array_map,array_filter,array_reduce, генераторы.
Безопасность и строгость
- В PHP до версии 7.4 отсутствовала поддержка типизированных свойств и параметров по умолчанию в сигнатурах, что увеличивало риски ошибок в условиях. Современный PHP (8.0+) позволяет указывать типы в параметрах функций и возвращаемых значениях, что делает условия более надёжными:
function process(int $age): string. - C# и TypeScript (надмножество JS) имеют статическую типизацию, что позволяет выявлять ошибки сравнения (
string == int) на этапе компиляции/анализа. - Python остаётся динамически типизированным, но с mypy можно получить проверку типов, близкую к статической.
Производительность
- Цикл
forс числовым счётчиком в PHP быстрееforeachпо массиву только если массив индексирован подряд ([0,1,2,…]). Для ассоциативных или разреженных массивовforeachэффективнее, так как обходит только существующие элементы. - В C# и Java JIT-компилятор может оптимизировать циклы до уровня машинных инструкций (векторизация, unrolling). PHP, будучи интерпретируемым (даже с OPcache), таких оптимизаций не делает.
- Генераторы в PHP, Python и JS работают сопоставимо по принципу: ленивая итерация, минимальный overhead на шаг.
Практические рекомендации и антипаттерны
Ниже — сводка проверенных практик, выработанных сообществом и подтверждённых статическим анализом (например, через Psalm, PHPStan):
- Предпочитайте
===и!==во всех условиях, кроме случаев, когда неявное приведение намеренно (редко). - Избегайте
and/or— их низкий приоритет нарушает ожидания, основанные на других языках. - Не модифицируйте итерируемую структуру внутри
foreach— это ведёт к неопределённому поведению. - Всегда
unset()послеforeach (&$ref)— это стандарт де-факто в PSR-совместимом коде. - Используйте
matchвместоswitch, если требуется только выбор значения и все варианты известны. - Для числовых диапазонов —
range()+foreach, а не ручнойfor, если производительность не критична:foreach (range(1, 100) as $i) { … } - В веб-контексте — валидируйте и приводите типы входных данных до условий, а не внутри них. Используйте
filter_input(),ctype_*,is_*функции.
Антипаттерны:
if ($var = getValue())— присваивание внутри условия (легко спутать с==). Современные IDE и анализаторы помечают это как ошибку.while (feof($fh))— неправильный способ чтения файла;feof()возвращаетtrueпосле попытки чтения за концом, а не до.for ($i=0; $i<count($arr); $i++)— вызовcount()на каждой итерации. Кэшируйте:$n = count($arr); for ($i=0; $i<$n; $i++).